import type { BamlRuntime, FunctionResult, BamlCtxManager, Image, Audio, Pdf, Video, ClientRegistry, Collector, FunctionLog } from "@boundaryml/baml"
import { toBamlError, BamlAbortError, type HTTPRequest } from "@boundaryml/baml"
import type { Checked, Check, RecursivePartialNull as MovedRecursivePartialNull } from "./types"
import type * as types from "./types"
import type {
  {%- for t in types %}{{ t }}{% if !loop.last %}, {% endif %}{% endfor -%}
} from "./types"
import type TypeBuilder from "./type_builder"
import { HttpRequest, HttpStreamRequest } from "./sync_request"
import { LlmResponseParser, LlmStreamParser } from "./parser"
import { DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX, DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME } from "./globals"
import type * as events from "./events"

/**
 * @deprecated Use RecursivePartialNull from 'baml_client/types' instead.
 * Example:
 * ```ts
 * import { RecursivePartialNull } from './baml_client/types'
 * ```
 */
export type RecursivePartialNull<T> = MovedRecursivePartialNull<T>;

type TickReason = "Unknown";

type BamlCallOptions<WatchersT = never> = {
  tb?: TypeBuilder
  clientRegistry?: ClientRegistry
  collector?: Collector | Collector[]
  env?: Record<string, string | undefined>
  tags?: Record<string, string>
  signal?: AbortSignal
  onTick?: (reason: TickReason, log: FunctionLog | null) => void
  watchers?: WatchersT
}

export class BamlSyncClient {
  private httpRequest: HttpRequest
  private httpStreamRequest: HttpStreamRequest
  private llmResponseParser: LlmResponseParser
  private llmStreamParser: LlmStreamParser
  private bamlOptions: BamlCallOptions

  constructor(private runtime: BamlRuntime, private ctxManager: BamlCtxManager, bamlOptions?: BamlCallOptions) {
    this.httpRequest = new HttpRequest(runtime, ctxManager)
    this.httpStreamRequest = new HttpStreamRequest(runtime, ctxManager)
    this.llmResponseParser = new LlmResponseParser(runtime, ctxManager)
    this.llmStreamParser = new LlmStreamParser(runtime, ctxManager)
    this.bamlOptions = bamlOptions || {}
  }

  withOptions(bamlOptions: BamlCallOptions) {
    return new BamlSyncClient(this.runtime, this.ctxManager, bamlOptions)
  }

  /*
  * @deprecated NOT IMPLEMENTED as streaming must by async. We
  * are not providing an async version as we want to reserve the
  * right to provide a sync version in the future.
  */
  get stream() {
    throw new Error("stream is not available in BamlSyncClient. Use `import { b } from 'baml_client/async_client")
  }

  get request() {
    return this.httpRequest
  }

  get streamRequest() {
    return this.httpStreamRequest
  }

  get parse() {
    return this.llmResponseParser
  }

  get parseStream() {
    return this.llmStreamParser
  }

  {% for func in functions %}
  {{ func.name }}(
      {% for (name, arg_type) in func.args -%}
      {{name}}{% if arg_type.meta().is_optional() %}?{% endif %}: {{arg_type.serialize_type(pkg)}},
      {%- endfor %}
      __baml_options__?: BamlCallOptions<{{ func.event_type_param() }}>
  ): {{func.return_type.serialize_type(pkg)}} {
    try {
      const options = { ...this.bamlOptions, ...(__baml_options__ || {}) }
      const signal = options.signal;

      if (signal?.aborted) {
        throw new BamlAbortError('Operation was aborted', signal.reason);
      }

      // Check if onTick is provided and reject for sync operations
      if (options.onTick) {
        throw new Error("onTick is not supported for synchronous functions. Please use the async client instead.");
      }

      const collector = options.collector ? (Array.isArray(options.collector) ? options.collector : [options.collector]) : [];
      const rawEnv = __baml_options__?.env ? { ...process.env, ...__baml_options__.env } : { ...process.env };
      const env: Record<string, string> = Object.fromEntries(
        Object.entries(rawEnv).filter(([_, value]) => value !== undefined) as [string, string][]
      );
      const raw = this.runtime.callFunctionSync(
        "{{func.name}}",
        {
          {% for (name, arg_type) in func.args -%}
          "{{name}}": {{name}}{% if arg_type.meta().is_optional() %}?? null{% endif %}{% if !loop.last %},{% endif %}
          {%- endfor %}
        },
        this.ctxManager.cloneContext(),
        options.tb?.__tb(),
        options.clientRegistry,
        collector,
        options.tags || {},
        env,
        signal,
        options.watchers,
      )
      return raw.parsed(false) as {{func.return_type.serialize_type(pkg)}}
    } catch (error: any) {
      throw toBamlError(error);
    }
  }
  {% endfor %}
}

export const b = new BamlSyncClient(DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME, DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_CTX)
